#ifndef Networking_h__
#define Networking_h__

#include <vector>
#include <assert.h>
#include <iostream>
#include <SDL_net.h>
#include <sstream>

class DBO
{
public:
	static void DebugOut(std::ostringstream& oss);
};
#define OUT_STRING(s) {}//{std::ostringstream oss; oss << s; DBO::DebugOut(oss);}
/*
    This is here because EVERYTHING includes Netowrking.h

    This allows us to essentially turn debug strings on or off.

    Anything that should ALWAYS be output, regardless of build
    should be std::cout'd. Anything that is just for debugging
    or help in understanding control flow should be OUT_STRING'd

*/

//And on with the networking...
namespace Networking
{

class SockMsg
{
public:
    SockMsg():m_index(0){}
    bool ReadFloat(float* t)
    {
        //Make sure we are still within our vector range
        if((unsigned int)m_index + sizeof(float) > m_data.size())
            return false;

        //read the int
        int test = 0;
        if(!ReadInt(&test))
            return false;

        //cast back to foat
        float f = (*(float*)&test);

        *t = f;

        return true;
    }

    bool ReadInt(int* t)
    {
        //Make sure we are still within our vector range
        if((unsigned int)m_index + sizeof(int) > m_data.size())
            return false;

        //casting it back from unsigned char*
        int i = *(int*)(&m_data[m_index]);
        //move the position along by the amount of bytes for an int
        m_index+=4;

        *t = SDLNet_Read32(&i);

        return true;
    }

    bool ReadString(std::string* t)
    {
        //first read in the length of the string
        int size = 0;
        if(!ReadInt(&size))
            return false;

        //Make sure we are still within our vector range
        if((unsigned int)m_index + size > m_data.size())
        {
            return false;
        }
        
        if(size == 0)
        {
            *t = "0 Length string received...";
            return true;
        }
        //casting it back from unsigned char*
        unsigned char* ch = &(m_data[m_index]);

        t->insert(t->begin(),ch, ch + size);

        //move the position along the vector
        m_index+= size;
        return true;
    }

    void WriteFloat( float f )
    {
        WriteInt(*(int*)&f);
    }

    void WriteInt( int i )
    {
        int newInt = 0;
        SDLNet_Write32(i,&newInt);
        // cast from the int
        unsigned char* ch = (unsigned char*)(&newInt);

        //Then insert the bytes
        m_data.insert(m_data.end(), ch, ch + 4);
    }

    void WriteString( const std::string& str )
    {
        //Cast string to new char array
        int size = str.size();

        //Insert the length of the String into the vector
        WriteInt(size);

        unsigned char* ch = (unsigned char*)(str.c_str());

        //Then insert the bytes
        m_data.insert(m_data.end(), ch, ch + size);

    }

    void Clear()
    {
        m_data.clear();
    }
    unsigned int Size() const {return m_data.size();}
    int GetIndex(){return m_index;}
    const unsigned char* Data() const {return &m_data[0];}
    std::vector<unsigned char>& GetData(){return m_data;}
private:
    std::vector<unsigned char> m_data; 
    int m_index;
};

class Socket
{
public:
    Socket() : m_open(false)
    {
        m_socketSet = SDLNet_AllocSocketSet(1);
    }
    ~Socket() 
    {
        if(m_open)
            Close();
        SDLNet_FreeSocketSet(m_socketSet);
    }
    void Invalidate()
    {
        m_open = false;
        SDLNet_TCP_DelSocket(m_socketSet, m_socket);
    }
    void Close()
    {
        m_open = false;
        SDLNet_TCP_DelSocket(m_socketSet, m_socket);
        SDLNet_TCP_Close(m_socket);   
    }
    bool OpenClient(const std::string& IP, int port)
    {
        IPaddress ip;
        OUT_STRING("Resolving Host...");
        SDLNet_ResolveHost(&ip, IP.c_str(), port);
        if(ip.host == INADDR_NONE || ip.host == 0)
        {
            return false;
        }
        OUT_STRING("Opening...");
        m_socket = SDLNet_TCP_Open(&ip); // BLOCKS
        if(m_socket == 0)
        {
            return false;
        }
        OpenBase();
        m_open = true;
        return true;
    }
    bool OpenServer(int port)
    {
        IPaddress ip;
        SDLNet_ResolveHost(&ip, INADDR_ANY,port);
        if(ip.host == INADDR_NONE)
        {
            return false;
        }

        m_socket = SDLNet_TCP_Open(&ip);
        if(m_socket == 0)
        {
            return false;
        }
        OpenBase();
        m_open = true;
        return true;
    }
    void SetSocket(TCPsocket sock)
    {
        m_socket = sock;
        m_open = true;
        OpenBase();
    }
    bool AcceptConnection(Socket* s)
    {
        TCPsocket sock = SDLNet_TCP_Accept(m_socket);
        if(sock)
        {
            s->SetSocket(sock);
            return true;
        }
        return false;
    }

private:
    void OpenBase()
    {
        SDLNet_TCP_AddSocket(m_socketSet, m_socket);
    }
    bool HasData()
    {
        int ret = SDLNet_CheckSockets(m_socketSet, 0);
        if(ret > 0)
            return true;
        return false;
    }

public:
    
    bool IsOpen(){return m_open;}
    bool Receive(SockMsg* msg)
    {
        //Now ready to use new_tcpsock as before
        static const int MAXLEN = 1024;
        char msgrec[MAXLEN];
        memset(msgrec,0,MAXLEN);
        int total = 0;
        while(HasData())
        {
            int result = SDLNet_TCP_Recv(m_socket,msgrec,MAXLEN); // No BLOCK
            if (result <= 0)
            {
                m_open = false;
                return false;
            }
            total += result;
            msg->GetData().insert(msg->GetData().end(),msgrec,msgrec + result);
        }
        if(total > 0)
            return true;
        return false;
    }
    bool BlockReceive(SockMsg* msg)
    {
        //Now ready to use new_tcpsock as before
        static const int MAXLEN = 1024;
        char msgrec[MAXLEN];
        memset(msgrec,0,MAXLEN);
        do
        {
            int result = SDLNet_TCP_Recv(m_socket,msgrec,MAXLEN);
            if (result <= 0)
            {
                return false;
            }
            msg->GetData().insert(msg->GetData().end(),msgrec,msgrec + result);
        }while(HasData());
        return true;
    }
    bool Send(const SockMsg& msg)
    {
        int result = SDLNet_TCP_Send(m_socket, msg.Data(), msg.Size());
        if(result != msg.Size())
        {
            OUT_STRING("Socket::Send failed, result size: " << result
                       << " , intended size: " << msg.Size());
            return false;
        }
        return true;
    }

private:
    TCPsocket m_socket;
    SDLNet_SocketSet m_socketSet;
    bool m_open;
};

} // Networking

#endif // Networking_h__